use sled::Db;
use sled::Tree;
use crate::common::*;

use super::*;

const FINGERPRINT_INDEX_STORE_NAME:&str = "fingerprint_store";
const RESEMBLANCE_INDEX_STORE_NAME_PREFIX:&str = "sf_store";

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SledConfig {
    pub fp_limit:usize,
    pub sf_limit:usize,
}

impl Default for SledConfig {
    fn default() -> Self {
        Self { fp_limit:1, sf_limit:1, }
    }
}

#[derive(Debug, Clone)]
pub struct SledFingerPrintStore {
    limit:usize,
    inner:Tree,
}

impl SledFingerPrintStore {
    pub fn open_tree(sled_db:&Db, limit:usize) -> IndexResult<Self> {
        let tree = sled_db.open_tree(FINGERPRINT_INDEX_STORE_NAME)?;
        Ok(Self { inner: tree, limit, })
    }

    fn update_chunk_info(old: Option<&[u8]>, id:ContainerId, limit:usize) -> Option<Vec<u8>> {
        let mut fingerprint_chunk_info = match old {
            Some(old) => bincode::deserialize(old).unwrap(),
            None => FingerPrintChunkInfo::new(),
        };
        fingerprint_chunk_info.push_id_with_limit(id, limit);
        Some(bincode::serialize(&fingerprint_chunk_info).unwrap())
    }
}

impl FingePrintStore for SledFingerPrintStore {
    fn insert_fp_with_container_id(&self, fp:&[u8], id:ContainerId) -> IndexResult<()> {
        self.inner.fetch_and_update(fp, |old|SledFingerPrintStore::update_chunk_info(old, id, self.limit))?;
        Ok(())
    }

    fn get_container_id_from_fp(&self, fp:&[u8]) -> IndexResult<Option<ContainerId>> {
        let ivec= self.inner.get(fp)?;
        if ivec.is_none() { return Ok(None); }
        let fingerprint_chunk_info:FingerPrintChunkInfo = bincode::deserialize(&ivec.unwrap()).unwrap();
        Ok(Some(fingerprint_chunk_info.id()))
    }
}

#[test]
fn test_sled_fingerprint_store() {
    let limit = 1;
    let index_path = "./test/index/";
    let db = sled::open(index_path).expect("db open error");
    let fp_store = SledFingerPrintStore::open_tree(&db, limit).expect("fp store open error");
    test_finger_print_store(fp_store).expect("test fingerprint store error");
    std::fs::remove_dir_all(index_path).unwrap();
}


#[derive(Debug, Clone)]
pub struct SledSuperFeatureStore {
    limit:usize,
    is_sequential:bool,
    inner:Vec<Tree>,
}

impl SledSuperFeatureStore {
    pub fn open_tree(sled_db:&Db, limit:usize, sf_config:ResemblanceConfig) -> IndexResult<Self> {
        // let tree = sled_db.open_tree(FINGERPRINT_INDEX_STORE_NAME)?;
        let mut inner = Vec::new();
        let is_sequential = sf_config.is_sequential();
        if is_sequential {
            for i in 0..sf_config.sf_len() {
                let tree_name = format!("{}{}",RESEMBLANCE_INDEX_STORE_NAME_PREFIX, i);
                let tree = sled_db.open_tree(tree_name)?;
                inner.push(tree);
            }
        } else {
            let tree = sled_db.open_tree(RESEMBLANCE_INDEX_STORE_NAME_PREFIX)?;
            inner.push(tree);
        }
        Ok(Self { inner, is_sequential, limit, })
    }

    fn update_chunk_info(old: Option<&[u8]>, id:ContainerId, limit:usize) -> Option<Vec<u8>> {
        let mut fingerprint_chunk_info = match old {
            Some(old) => bincode::deserialize(old).unwrap(),
            None => SuperFeatureChunkInfo::new(),
        };
        fingerprint_chunk_info.push_id_with_limit(id, limit);
        Some(bincode::serialize(&fingerprint_chunk_info).unwrap())
    }

    
    fn search_sf_from_tree(tree:&Tree, feature:u64) -> IndexResult<Option<ContainerId>> {
        let ivec= tree.get(feature.to_le_bytes())?;
        if ivec.is_none() { return Ok(None); }
        let fingerprint_chunk_info:FingerPrintChunkInfo = bincode::deserialize(&ivec.unwrap()).unwrap();
        Ok(Some(fingerprint_chunk_info.id()))
    }
}

impl SuperFeatureStore for SledSuperFeatureStore {
    fn insert_sf_with_container_id(&self, sf:&[u64], id:ContainerId) -> IndexResult<()> {
        for (i, &f) in sf.iter().enumerate() {
            let tree = if self.is_sequential { &self.inner[i] } else { &self.inner[0] };
            tree.fetch_and_update(f.to_le_bytes(), |old|Self::update_chunk_info(old, id, self.limit))?;
        }
        Ok(())
    }

    fn get_container_id_from_sf(&self, sf:&[u64]) -> IndexResult<Option<ContainerId>> {
        for (i, &f) in sf.iter().enumerate() {
            let tree = if self.is_sequential { &self.inner[i] } else { &self.inner[0] };
            let result = Self::search_sf_from_tree(tree, f)?;
            if result.is_some() { return Ok(result); }
        }
        Ok(None)
    }
}

#[test]
fn test_sled_superfeature_store() {
    use crate::common::{Ntransform, ResemblanceConfig};
    let sf_config = ResemblanceConfig::Ntransform(Ntransform);
    let limit = 1;
    let index_path = "./test/index/";
    let db = sled::open(index_path).expect("db open error");
    let sf_store = SledSuperFeatureStore::open_tree(&db, limit, sf_config).expect("sf store open error");
    test_superfeature_store(sf_store, sf_config).expect("test sf store error");
    std::fs::remove_dir_all(index_path).unwrap();
}